/* Customized MPI Wrapper */ 

#include "array.h"

#define ROOT 0
#define SUCCESS 0
#define ERROR -1
#define OPER_COUNTER(op, name) {name, 0, NULL}

static int initialized = 0;
static double start_time = 0.0;
static double end_time = 0.0;
static double entry_time = 0.0;
static double exit_time = 0.0;
static const char* dot_fname = "mpi_graph.dot";
static const char* pdf_fname = "mpi_graph.pdf";
static const char* critical_fname = "critPath.out";
static const char* stat_fname = "stats.dat";
static int vid = 0;

#define OPER_COUNTER(op, name) {name, 0, NULL}

enum MPI_ID{
{{foreachfn fun MPI_Barrier MPI_Alltoall MPI_Scatter MPI_Gather MPI_Reduce MPI_Allreduce MPI_Send MPI_Recv MPI_Isend MPI_Irecv MPI_Wait MPI_Waitall MPI_Init MPI_Finalize}}    {{fun}}_counter, 
{{endforeachfn}}
};

typedef struct Operation_counter {
    char *name;
    unsigned long long value;
    Array *exec_times;
} Operation_counter_t;

Operation_counter counters[] = {
{{foreachfn fun MPI_Barrier MPI_Alltoall MPI_Scatter MPI_Gather MPI_Reduce MPI_Allreduce MPI_Send MPI_Recv MPI_Isend MPI_Irecv MPI_Wait MPI_Waitall MPI_Init MPI_Finalize}}    OPER_COUNTER({{fun}}_counter, "{{fun}}"),
{{endforeachfn}}
};

void Pre_MPI(void);
int Post_MPI(enum MPI_ID id);

// Edge list of a graph
typedef struct Edge_t Edge;
typedef struct Vertex_t Vertex; 
struct Edge_t
{
    double weight;
    Vertex* dest_vertex;
    Edge *edge_ptr;
};

// Vertex list of a graph
struct Vertex_t
{
    int out_degree;
    int in_degree;
    int vertex_id;
    int visited;
    Edge *edge_ptr;
    Vertex *next;
    Vertex *prev_crit_vertex;
    double crit_duration;
    int on_crit_path;
    // MPI information 
    int rank;
    int dest_rank;
    long long unsigned sequence_id;
    double weight;
    MPI_ID mpi_id;
    long long unsigned msg_size;
};

// Graph is represented by a list of vertex
typedef struct Graph_t
{   
    int num_vertices;
    Vertex *vertex_head;
} Graph;

// Linked path represents a path of a graph
typedef struct Node_t Node;

// A node of a path (represented by a vertex)
struct Node_t
{
    Vertex *vertex;
    Node *next;
};

typedef struct Path_t
{
    Node *head;
} Path;

////////////////////////////////////////////////////////////
////                Graph Operations                    ////
////////////////////////////////////////////////////////////

Graph* create_graph();

// Initialize a vertex to default values with id
void init_vertex
(
    Vertex *v, 
    int vid, 
    int rank, 
    double vtime, 
    MPI_ID id,
    long long unsigned sequence_id
);

// Insert a vertex to graph
int insert_vertex(Graph *graph, Vertex* vertex);

// Get the last vertex of graph
Vertex* get_last_vertex(const Graph* graph);

// Insert an edge (vertex1, vertex2) to graph
int insert_edge
(
    Graph *graph, 
    Vertex *vertex1, 
    Vertex *vertex2, 
    double weight
);

// Find the longest path (Critical path) of a graph
int compute_critical_path
(
    const Path* topo_path, 
    Path* crit_path,
    double *length
);

// Sort the vertices in topological order to path
void topological_sort
(
    const Graph *graph, 
    Path *path
);

// Helper function for topological sort
void visit(Vertex *vetex, Path *path);

// Print the graph
void print_graph(Graph *graph);

// Write graph to dot format and creates a pdf file
int generate_graph
(
    const Graph *graph, 
    const char* dotName, 
    const char* pdfName
);

// Output the critical path into a file
int output_crit_path
(
    const Path* crit_path,
    const char* fname
);

void free_graph(Graph *graph);

/* \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ */

// Insert a vertex to the beginning of a path
void insert(Path *path, Vertex* vertex);

// Append a vertex to the end of a path
void append(Path *path, Vertex* vertex);

// Print path
void print_path(Path *path);

// Free path
void free_path(Path *path);

void Pre_MPI(void);
int Post_MPI(MPI_ID id);

// Display the median, average, min and max of MPI operation execution time 
void output_mpi_statistics(const char* fname);

Graph * per_rank_graph = NULL;
Graph * root_graph = NULL;


///////////////////////////////////////////////////////////////////////////////
//                               Add generic wrapper                         //
///////////////////////////////////////////////////////////////////////////////

{{fn foo MPI_Barrier MPI_Alltoall MPI_Scatter MPI_Gather MPI_Reduce MPI_Allreduce MPI_Send MPI_Recv MPI_Isend MPI_Irecv MPI_Wait MPI_Waitall MPI_Init MPI_Finalize}}
  
    Pre_MPI();
    {{callfn}}
    Post_MPI({{foo}}_counter);
    
{{endfn}}

